#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h> 
#include <netinet/ip.h>
#include <netinet/in_systm.h>
#include <netinet/ip_icmp.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#define BUFSIZE 1500
char sendbuf[BUFSIZE];
char *host;
int nsent = 0;	// 每次发送递增1
pid_t pid;
int sockfd;
int verbose;
void sig_alrm(int signo)
{
    send_v4();
    alarm(1);	// 每间隔1秒发送一次SIGALRM信号
    return;
}
struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype)
{
	int n;
	struct addrinfo hints, *res;
	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_CANONNAME;	// 返回主机的规范名字
	hints.ai_family = family;
	hints.ai_socktype = socktype;
	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		return NULL;
	return res;
}
char* sock_ntop_host(const struct sockaddr *sa, socklen_t salen)
{
    static char str[128];		/* Unix domain is largest */
	switch (sa->sa_family)
	{
		case AF_INET:
		{
			struct sockaddr_in	*sin = (struct sockaddr_in *)sa;
			/*IP地址数值格式转表达格式*/
			if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
			{
				return NULL;
			}
			return str;
		}			
	}
    return NULL;
}
int datalen = 56;
int main(int argc, char** argv)
{
    int c;
    while ((c = getopt(argc, argv, "v")) != -1)
    {
        switch (c)
        {
        case 'v':
            verbose++;
            break;
        case '?':
            printf("unrecognized option: %c\n", optopt);
            exit(-1);
        }
    }
    // 获取主机参数
    host = argv[optind];
    pid = getpid() && 0xffff;
    signal(SIGALRM, sig_alrm);
	// 根据主机名和获取addrinfo
	struct addrinfo * ai = host_serv(host, NULL, 0, 0);	// 获取主机名相关的addrinfo结构
	// 从addrinfo中提取ip地址到h
	char * h = sock_ntop_host(ai->ai_addr, ai->ai_addrlen);	// 返回套接字关联的IP地址
	printf("PING %s (%s): %d data bytes\n", 
			ai->ai_canonname ? ai->ai_canonname : h, h, datalen);
	// 长度取决于ip类型                           
	readloop();
    return 0;
}
void proc_v4(char * ptr, ssize_t len, struct msghdr * msg, struct timeval * tvrecv)
{
    int hlen1, icmplen;
	double rtt;
	struct ip *ip;
	struct icmp *icmp;
	struct timeval *tvsend;
	// 验证报文合法性
	ip = (struct ip *)ptr;
	hlen1 = ip->ip_hl << 2;	// ip头部长度，ip头部单位是4字节，所以需要乘以4
	icmp = (struct icmp *)(ptr + hlen1);	
	if (icmp->icmp_type == ICMP_ECHOREPLY)	// ICMP回显应答
    {
		tvsend = (struct timeval *)icmp->icmp_data;		// 获取报文发送时间
		tv_sub(tvrecv, tvsend);		// 计算RTT
		rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
		// 打印出回显应答报文的数据长度，序列号ttl，报文往返时间TTL
		printf("%d bytes from %s: seq = %u, ttl = %d, rtt = %.3f ms\n", 
				icmplen, sock_ntop_host(pr->sarecv, pr->salen), 
				icmp->icmp_seq, ip->ip_ttl, rtt);
	}
}
// icmp头部包括 类型1字节 代码1字节 校验码2字节
void send_v4(void)
{
	struct icmp *icmp;	// icmp头部
	icmp = (struct icmp *)sendbuf;	// 1500字节的静态空间
	icmp->icmp_type = ICMP_ECHO;	// 类型 = 8
	icmp->icmp_code = 0;	// 代码 = 0 请求回显
	icmp->icmp_id = pid;	// 标识符2字节 设置为发送进程的pid
	icmp->icmp_seq = nsent++;	// 序列号2字节
	memset(icmp->icmp_data, 0x0, datalen);	// 数据部分长度56字节
	gettimeofday((struct timeval *)icmp->icmp_data, NULL);	// 填充发送时间戳
	int len = 8 + datalen;	// ICMP报文长度56+8=64字节
	icmp->icmp_cksum = 0;
	icmp->icmp_cksum = in_cksum((u_short *)icmp, len);	// 计算校验和
	// sendbuf虽然有1500字节，但只发送len长度的icmp报文
    sendto(sockfd, sendbuf, len, 0, 发送套接字addr, 前者大小);
}
void readloop(void)
{
	char recvbuf[BUFSIZE];
	char controlbuf[BUFSIZE];
	struct msghdr msg;
	struct iovec iov;
	ssize_t n;
	struct timeval tval;
	// 创建原始套接字，需要超级用户权限
	sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	setuid(getuid());	// 降低权限
	sig_alrm(SIGALRM);  // 发送ICMP回显请求
	iov.iov_base = recvbuf;
	iov.iov_len = sizeof(recvbuf);
	msg.msg_name = pr->sarecv;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = controlbuf;
	while (1)
	{
		msg.msg_namelen = pr->salen;
		msg.msg_controllen = sizeof(controlbuf);
		n = recvmsg(sockfd, &msg, 0);	// 接收到达接口的ICMP报文
		if (n < 0)
		{ 
			if (errno == EINTR)
				continue;
			else
				printf("recvmsg error");
		}
		gettimeofday(&tval, NULL);	// 获取报文到达时间
		proc_v4(recvbuf, n, &msg, &tval);	// 处理接收的报文
	}
}
void tv_sub(struct timeval *out, struct timeval *in)
{
    if ((out->tv_usec -= in->tv_usec) < 0)
    {
        --out->tv_sec;
        out->tv_usec += 1000000;
    }
    out->tv_sec -= in->tv_sec;
}
uint16_t in_cksum(uint16_t *addr, int len)
{
    int nleft = len;
    uint32_t sum = 0;
    uint16_t *w = addr;
    uint16_t answer = 0;
    while (nleft > 1)
    {
        sum += *w++;
        nleft -= 2;
    }
    if (nleft == 1)
    {
        *(unsigned char*)(&answer) = *(unsigned char *)w;
        sum += answer;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = ~sum;
    return (answer);
}